package tr.edu.ege.seagent.hobnobontology.instance;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import tr.edu.ege.seagent.hobnobontology.schema.HobNobVocabulary;

import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.vocabulary.RDF;

public class HobNobModelSearcher {

	/**
	 * Searched population limit.
	 */
	private static final int DESIRED_POPULATION = 3000000;

	/**
	 * Model to search.
	 */
	private Model sampleModel;

	/**
	 * Logger to logging.
	 */
	private Logger logger;

	/**
	 * Creates a {@link HobNobModelSearcher} for
	 * {@link HobNobSampleModelCreator} model.
	 */
	public HobNobModelSearcher() {
		HobNobSampleModelCreator modelCreator = new HobNobSampleModelCreator();
		this.sampleModel = modelCreator.createNewInstance();
		setLogger();

	}

	/**
	 * Creates a {@link HobNobModelSearcher} for the given ontology model.
	 * 
	 * @param sampleModel
	 *            model to search.
	 */
	public HobNobModelSearcher(Model sampleModel) {
		this.sampleModel = sampleModel;
		setLogger();

	}

	/**
	 * Creates a logger for this instance with info level.
	 */
	private void setLogger() {
		this.logger = Logger.getLogger(HobNobModelSearcher.class);
		DOMConfigurator.configure("log4j.xml");
		// this.logger.setLevel(Level.INFO);
		// PropertyConfigurator.configure(HobNobModelSearcher.class.getName());
	}

	/**
	 * This function provide us to return literal that from given resource with
	 * given resource's property.
	 * 
	 * @param resource
	 *            resource which we want to take its literal.
	 * @param propertyURI
	 *            URI of property.
	 * @return literal was found.
	 */
	public Literal getLiteral(Resource resource, String propertyURI) {
		// gets a property statement which resource and property URI's is given
		Statement propertyStmt = sampleModel.getProperty(resource,
				ResourceFactory.createProperty(propertyURI));
		// returns this statement's literal.
		return propertyStmt.getObject().asLiteral();
	}

	/**
	 * This function provides us to find population which location was given.
	 * 
	 * @param locationResource
	 *            resource of location which population is wanted to find.
	 * @return population value.
	 */
	public int getPopulation(Resource locationResource) {

		logger.debug("Given location resource is controlling that being location or not.");
		// type control of given resource...
		if (isInstanceOf(locationResource, HobNobVocabulary.LOCATION_URI)) {

			logger.debug(MessageFormat.format(
					"Given resource \"{0}\" is location.",
					locationResource.getURI()));
			logger.debug(MessageFormat
					.format("Statement which is subject is \"{0}\" and predicate is LOCATION_HAS_DBPEDIA_URI is being created.",
							locationResource.getURI()));

			// gets a property statement that it's resource is location and it's
			// property
			// is a LOCATION_HAS_DBPEDIA_URI
			Statement dbpediaStatament = getPropertyStatement(locationResource,
					HobNobVocabulary.LOCATION_HAS_DBPEDIA_URI);
			logger.debug("Statement was created.");

			logger.debug("Object of property statement is being gotten.");
			// gets a object of this statement
			Resource place = dbpediaStatament.getObject().asResource();

			logger.trace(MessageFormat.format(
					"Object of property statement that \"{0}\" was gotten.",
					place.getURI()));

			logger.debug(MessageFormat.format(
					"Sample model is reading \"{0}\" ", place.getURI()));
			// reads dbpedia resource
			sampleModel.read(place.getURI());

			logger.debug(MessageFormat.format("Sample model read \"{0}\" ",
					place.getURI()));
			// gets a population of given dbpedia resource.
			logger.trace("Population total is being gotten from dbpedia resource.");
			int populationTotal;
			
			if (place.hasProperty(ResourceFactory.createProperty(HobNobInstanceVocabulary.POPULATION_TOTAL_URI)))
					 {
				populationTotal = getLiteral(place,
						HobNobInstanceVocabulary.POPULATION_TOTAL_URI).getInt();
			} else {
				populationTotal = getLiteral(place,
						HobNobInstanceVocabulary.POPULATION_METRO_URI).getInt();
			}

			logger.trace(MessageFormat.format(
					"Population total was gotten and its value is \"{0}\"",
					String.valueOf(populationTotal)));
			return populationTotal;
		} else {
			logger.debug(MessageFormat.format(
					"Given resource \"{0}\" is not a location. ",
					locationResource.getURI()));
			throw new IllegalArgumentException(
					"Given resource must be a location!");
		}
	}

	/**
	 * This function provide us to return statement which is related with given
	 * resource and propertyURI
	 * 
	 * @param resource
	 *            which we want to find it's property
	 * @param propertyURI
	 *            URI of property
	 * @return statement which includes given resource and it's property.
	 */
	public Statement getPropertyStatement(Resource resource, String propertyURI) {
		return resource.getProperty(sampleModel.getProperty(propertyURI));
	}

	/**
	 * This function returns TRUE if the resource's property's class type is
	 * given classURI.
	 * 
	 * @param resource
	 *            which we want to learn its type.
	 * @param classURI
	 *            URI of class which can be
	 *            {@link HobNobVocabulary#NOTIFICATION_URI} or
	 *            {@link HobNobVocabulary#LOCATION_URI} etc.
	 * 
	 * @return true @if resource type is given classURI's type
	 * @return false @if resource type isn't given classURI's type
	 */
	public boolean isInstanceOf(Resource resource, String classURI) {
		return resource
				.hasProperty(RDF.type, sampleModel.getResource(classURI));
	}

	/**
	 * This function provide us to find notification that notification was made
	 * at location has more than three million population
	 * 
	 * @return
	 */
	public List<Resource> searchNotificationAtLocationHasMoreThanThreeMillionPopulation() {

		// get all subjects in the model...
		List<Resource> subjectList = sampleModel.listSubjects().toList();
		List<Resource> notifications = new ArrayList<Resource>();
		logger.debug("Subjects are being traversed.");

		// traverse subjects to find notifications...
		for (Resource subjectRsc : subjectList) {
			logger.debug(MessageFormat
					.format("\"{0}\" Resource is being searched for it is notification at location has more than three million population or not.",
							subjectRsc.getURI()));
			Resource notification = isNotificationAtLocationHasMoreThanThreeMillionPopulation(subjectRsc);
			if (notification != null) {
				logger.trace("Searching resource is notification that at location has more than three million population.");
				logger.info("Resource which was finded is "
						+ notification.getURI());
				notifications.add(notification);
			} else {
				logger.debug("Searching resource is not a notification resource.");
			}
		}
		logger.debug("Traversing finished. Finded notifications is being returned.");
		return notifications;
	}

	/**
	 * Gets a notification if all conditions are true. Condition : Is this
	 * resource is a notification instance ? Condition : Is this resource has a
	 * location which population is more than desired population
	 * 
	 * @param resource
	 *            notification instance
	 * @if population > DESIRED_POPULATION @return notification
	 * @else @return null
	 */
	public Resource isNotificationAtLocationHasMoreThanThreeMillionPopulation(
			Resource resource) {
		logger.debug("Given subject resource is controlling that being notification or not.");
		// check that the resource is a notification...
		if (isInstanceOf(resource, HobNobVocabulary.NOTIFICATION_URI)) {

			logger.debug(MessageFormat.format(
					"Given resource \"{0}\" is notification.",
					resource.getURI()));

			logger.trace("Population is being gotten");
			// gets population of given resource's location's...
			int population = getPopulation(getLocation(resource));
			logger.trace("Population was gotten.");
			logger.trace("Population is being cheched that is greater than desired population or not.");
			// check that the population is greater than desired
			// population...
			if (population > DESIRED_POPULATION) {
				logger.info(MessageFormat
						.format("Population is greater than desired population and its value is \"{0}\"",
								String.valueOf(population)));
				logger.debug("Resource is being returned.");
				return resource;
			} else {
				logger.debug("Population is not graater than desired population.");
			}
		}
		logger.debug("Return null cause given resource is not a notification resource.");
		return null;
	}

	/**
	 * Gets a location that given resorce's.
	 * 
	 * @param resource
	 *            which it's location is wanted.
	 * @return location was found.
	 */
	public Resource getLocation(Resource resource) {
		Statement locationStatement = getPropertyStatement(resource,
				HobNobVocabulary.NOTIFICATION_AT_LOCATION_URI);
		Resource locationRsc = locationStatement.getObject().asResource();
		return locationRsc;
	}

}
